#pragma once
#pragma once
#pragma once
#include <memory>
#include <unordered_map>
#include <functional>

#include <unistd.h>
#include <sys/epoll.h>

#include "Socket.hpp"
#include "Server.hpp"
#include "ClientInfo.hpp"
#include "Log.hpp"
#include "Error.hpp"
#include "Protocol.hpp"
#include "Epoll.hpp"
#include "Util.hpp"

/*
    多路转接之 select
*/
//

// 动态增长来管理 用户关心的文件描述符
// 在达到阈值之前2倍增长，超过阈值线性增长
const int efd_init = 2;
const int efd_threshold = 20;
const int efd_increase = 4;
const int efd_default = -1;

typedef struct epoll_event type_t;

// 先写出echo服务器
// 再添加其他复杂业务

// 回调函数，处理业务
using func_t = std::function<Response(const Request &)>;
// 连接事件处理方法
using func = std::function<void(int)>;

struct Connection
{
    Connection()
    {
    }

    Connection(int fd)
        : _fd(fd)
    {
    }

    Connection(int fd, Client cli)
        : _cli(cli), _fd(fd)
    {
    }
    void Register(func reader, func writer, func excepter)
    {
        _reader = reader;
        _writer = writer;
        _excepter = excepter;
    }

    int _fd;
    // 我们应该也把事件添加到连接集合中
    Client _cli;
    std::string inbuf;  // 输入缓冲区，接收请求的缓冲区
    std::string outbuf; // 输出缓冲区，存放响应的缓冲区
    func _reader;
    func _writer;
    func _excepter;
};

class Reactor : public Server
{
public:
    using ptr = std::unique_ptr<Reactor>;
    Reactor()
    {
        Init();
    }

    Reactor(func_t cb)
        : _cb(cb)
        ,_epoll(EPOLL_TYPE::ET)
    {
        InitServer();
        Init();
    }
    void Init()
    {
        _cur_sz = 1;
        _events_sz = efd_init;
        _events = new type_t[efd_init];
        _listenfd = _listensock.Fd();
        cout << "_listenfd = " << _listenfd << endl;
        // 1.创建epoll模型
        _epoll.EpollCreate();
        // 2.让epoll关心 监听套接字的读事件
        _epoll.EpollCtl(_listenfd, EPOLL_CTL_ADD, EPOLLIN);
        // 3. 创建监听套接字的连接
        Connection con(_listenfd);
        // 4.并绑定监听套接字的回调方法
        con.Register(std::bind(&Reactor::AcceptHandler, this, std::placeholders::_1),
                     std::bind(&Reactor::WriteHandler, this, std::placeholders::_1),
                     std::bind(&Reactor::ExcepHandler, this, std::placeholders::_1));
        // 5.加入连接集合进行管理
        _connections[_listenfd] = con;
        // 6.设置为非阻塞
        if(_epoll.EpollType() == EPOLL_TYPE::ET)
            Util::SetNonBlock(_listenfd);
    }
    // 此时SelectServer再进行accept时，不会被阻塞了。
    // 因为select告知我listenfd，有事件就绪了，此时
    // 我进行读取即可。

    bool AddSpace()
    {
        int newsz = _events_sz < efd_threshold ? _events_sz * 2 : _events_sz + efd_increase;
        int n = _events_sz;
        type_t *tmp = new type_t[newsz];
        // 扩容的时候， _events 可能是有数据的!!! 因此是要发生拷贝！
        std::copy(_events, _events + _events_sz, tmp);
        delete[] _events; // 拷贝完成后释放原始空间！！！

        _events = tmp;
        _events_sz = newsz;
        return true;
    }

    void AcceptHandler(int fd)
    {
        while(true)
        {
            Client cli;
            int sock = _listensock.Accept(&cli._ip, &cli._port);
            if(sock >= 0)
            {
                int n = _events_sz;
                LOG(INFO, "新的连接-%d 到来...",sock);

                if (_cur_sz >= _events_sz)
                {
                    AddSpace();
                    LOG(INFO, "扩容成功");
                }
                _cur_sz++;
                // 新的连接到来后：
                // 1.创建连接对象
                Connection con(sock, cli);
                // 2.注册回调机制
                con.Register(std::bind(&Reactor::ReadHandler, this, std::placeholders::_1),
                            std::bind(&Reactor::WriteHandler, this, std::placeholders::_1),
                            std::bind(&Reactor::ExcepHandler, this, std::placeholders::_1));
                // 3.加入连接集合
                _connections[sock] = con;
                // 4.让epoll模型关心
                // 多路转接中，写事件不常设关心状态，因为大部分写是件是就绪的
                // 如果关心，那么每次epoll都会告知我连接就绪，白白浪费cpu资源
                _epoll.EpollCtl(sock, EPOLL_CTL_ADD, EPOLLIN);
                // 5.设置为非阻塞
                if(_epoll.EpollType() == EPOLL_TYPE::ET)
                    Util::SetNonBlock(sock);
            }
            else
            {
                if(errno == EINTR) // 表示当前读取时，被信号中断，重新再读取一次
                    continue;
                else if(errno == EAGAIN) // 
                    break;
                else
                {
                    LOG(ERROR,"accept fail... %d-%s",errno,strerror(errno));
                }
            }
        }

    }
    void IsEnableWriteEvent(int fd, bool flag)
    {
        // 修改：曾经对这个文件描述符关心了，此时我要对它关心的事件进行一个修改
        if (flag)
            _epoll.EpollCtl(fd, EPOLL_CTL_MOD, EPOLLOUT | EPOLLIN);
        else
            _epoll.EpollCtl(fd, EPOLL_CTL_MOD, EPOLLIN);
    }
    // 1.先测试echo 服务器
    // 2.再进行CacularServer
    void ReadHandler(int fd)
    {
        Connection con = _connections[fd];
        // ET模式下,
        while(true)
        {
            // 此时进行读，是不会被阻塞的，因为select通知我读事件已经就绪了
            // recv(int sockfd, void *buf, size_t len, int flags);
            char buf[10];
            // -1：为\0预留空间
            int n = recv(fd, buf, sizeof(buf) - 1, 0);
            if (n == 0)
            {
                LOG(INFO, "对端[%s-%d] 关闭", con._cli._ip.c_str(), con._cli._port);
                con._excepter(fd);
            }
            else if (n > 0)
            {
                buf[n] = 0;
                // cout << "buf" << buf << endl;
                // 将接收过来的数据存放到输入缓冲区
                con.inbuf += buf;
                cout << "GetMsg before:: inbuf" << con.inbuf << endl;

                // 边读取边解析
                std::string msg; // 报文
                if (Protocol::GetMsg(con.inbuf, &msg) == false)
                {
                    // 表示缓冲区内部没有一份完整的报文
                    LOG(INFO,"没有读取到完成的报文");
                    continue;
                    // return;
                }
                // cout << "GetMsg after:: inbuf" << inbuf << endl;
                // cout << "msg  " << msg << endl;

                // 获取到完整的报文
                // 获取有效载荷
                std::string body = Protocol::DelHeader(msg);
                // 反序列化
                Request req;
                req.DisSerialize(body);
                // 得到请求，处理请求,得到响应
                Response resp = _cb(req);
                // using callback = std::function<Response(const Request&)>;
                // 序列化
                std::string resp_str;
                resp.Serialize(&resp_str);
                // 添加报头
                Protocol::AddHeader(resp_str);
                // 在多路转接中写事件一般不关心
                // 因为大部分情况下，写事件是就绪的，对方的是有足够的缓冲区的
                // 如果写事件就绪，多路转接每次都会提醒我写事件就绪，白白浪费资源
                n = send(fd, resp_str.c_str(), resp_str.size(), 0);
                if (n == -1)
                {
                    // 发送失败，我再打开对写事件的关心，并把未发送成功的数据加入到输出缓冲区中
                    IsEnableWriteEvent(fd, true);
                    con.outbuf += resp_str;
                    LOG(ERROR, "send fail %d-%s, add into inbuf", errno, strerror(errno));
                }
                else
                {
                    // LOG(INFO, "发送成功 n = %d", n);
                }
            }
            else
            {
                LOG(INFO, "recv failed %d-%s", errno, strerror(errno));
                // 当文件描述符为非阻塞的,当读取不到数据时,返回-1,错误码被设置为EAGAIN
                // 表示：我读了,但是已经没有数据了,请再读一次
                // 在非阻塞下,当errno = EAGAIN时,则表明没有数据可读,则可跳出循环
                if(errno == EAGAIN)
                {
                    LOG(INFO,"循环读取,读取结束");
                    break;
                }
                else if(errno == EINTR) // EINTR: 表示当前读取时,被信号中断了,需要重新再读取
                    continue;
                else
                    // 出异常了，直接执行该连接的异常处理
                    con._excepter(fd);
            }
        }

    }
    void WriteHandler(int fd)
    {
        Connection con = _connections[fd];
        while(true)
        {
            // outbuf中存储的都是，处理好的响应报文
            // 我们直接全部发送
            int n = send(fd, con.outbuf.c_str(), con.outbuf.size(), 0);
            if (n > 0)
            {
                // 如果outbuf中的数据已经全部发送成功了
                // 则关闭对写事件的关心
                if (n == con.outbuf.size())
                    IsEnableWriteEvent(fd, false);
                // 发送成功，将数据从outbuf中删除
                con.outbuf.erase(0, n);
                if(con.outbuf.size() == 0)
                    break;
            }
            else
            {
                LOG(INFO, "send failed %d-%s", errno, strerror(errno));
                if(errno == EINTR) // 当前写入被信号中断,重新发送
                    continue;
                else if(errno == EAGAIN) // 当前写入对方缓冲区空间不够了,请重新写入
                    break;               // 当对面缓冲区被写满时,我们就可以break了
                else    // 出异常了，直接执行该连接的异常处理
                    con._excepter(fd);
            }
        }
    }
    
    bool IsExitConnction(int fd)
    {
       return  _connections.find(fd) != _connections.end();
    }


    void EvenHandler(int n)
    {

        for (int i = 0; i < n; i++)
        {
            ShowRevent(n);
            int fd = _events[i].data.fd;
            uint32_t event = _events[i].events;
            auto pos = _connections.find(fd);
            Connection con = _connections[fd];
            LOG(INFO,"%d-对应事件开始处理",fd);

            // 读事件就绪
            // 当读写方法和连接对象进行绑定后
            // 只要某个事件就绪，我们直接通过回调执行对应的事件函数
            // 如果读事件就绪时，我们并不需要判断是监听套接字，还是普通套接字

            // 当事件就绪就绪时，并不是if-else的关系，而是 if if 的关系
            // 在处理对应的事件时，要保证fd对应的连接是在连接集合中的！
            // 因为这是if关系，如果对应的连接不存在，此时对事件做处理
            // 那么一定是有问题的
            // 当连接出现异常时，我们可以让这个异常的处理在读事件，写事件中处理
            if (event & EPOLLERR || event & EPOLLHUP)
            {
                LOG(INFO, "%d-读事件就绪", fd);
                event |= EPOLLIN | EPOLLOUT;
            } 
            //读事件
            if (event & EPOLLIN && IsExitConnction(fd))
            {
                LOG(INFO, "%d-读事件就绪", fd);
                con._reader(fd);
            }
            // 写事件就绪
            if (event & EPOLLOUT && IsExitConnction(fd))
            {
                LOG(INFO, "%d-写事件就绪", fd);
                con._writer(fd);
            }



            // if (event & EPOLLIN)
            // {
            //     LOG(INFO, "%d-读事件就绪", fd);
            //     con._reader(fd);
            // } // 写事件就绪
            // else if (event & EPOLLOUT)
            // {
            //     LOG(INFO, "%d-写事件就绪", fd);
            //     con._writer(fd);
            // }
            // else if (event & EPOLLERR || event & EPOLLHUP) // 写一个|？是什么意思
            // {
            //     LOG(INFO, "%d-读事件就绪", fd);
            //     con._excepter(fd);
            // } 
            LOG(INFO,"%d-事件处理完成",fd);
        }
    }

    void ExcepHandler(int fd)
    {
        LOG(INFO, "异常处理");
        // 1.让epoll取消对该文件描述符的关心
        _epoll.EpollCtl(fd, EPOLL_CTL_DEL, 0);
        // 2.从连接集合中删除对该连接的管理
        _connections.erase(fd);
        // 3.关闭对应的套接字
        close(fd);
        // 4. 当前个数进行--
        _cur_sz--;

    }

    // 事件派发器
    // 让select 关心用户想要关心的文件描述符的读事件，写事件，异常事件
    // 如果该文件描述符上的某个事件就绪后，进行事件派发让它处理对应的事件
    void Dispatcher()
    {
        LOG(INFO, "epoll server is running...");
        while (true)
        {
            int timeout = 2;
            // epoll将事件就绪的fd放入 events中告知用户
            int n = _epoll.EpollWait(_events, timeout);
            // cout << "n = " << n << endl;
            ShowRevent(n);
            if (n == -1)
            {
                LOG(INFO, "select 失败");
                LOG(ERROR, "%d - %s", errno, strerror(errno));
            }
            else if (n > 0)
            {
                LOG(INFO, "有事件就绪了");
                EvenHandler(n);
            }
            else
            {
                LOG(INFO, "没有事件就绪");
            }
            LOG(INFO,"-----------------");
            sleep(1);
        }
    }
    void ShowRevent(int n)
    {
        cout << "就绪的文件描述符" << endl;
        for (int i = 0; i < n; i++)
        {
            cout << _events[i].data.fd << " ";
        }
        cout << endl;
    }
    virtual ~Reactor()
    {
        delete[] _events;
    }

private:
    // 这样管理太分散了
    // int *_fds;
    // int fd_max;
    // int _fds_sz;
    int _events_sz; // 总数
    int _cur_sz;    // 当前个数 --> 用来判断是否进行扩容
    int _listenfd;
    std::unordered_map<int, Connection> _connections;
    Epoll _epoll;
    type_t *_events; // epoll通知我的事件集合
    func_t _cb;
};